DOM
主要部分: DOM 事件的级别,DOM 事件模型,DOM 事件流,DOM 事件捕获的具体流程,Event 对象的常见应用,自定义事件 这几个部分的内容。
什么是 window 对象? 什么是 document 对象
window
- Window 对象表示当前浏览器的窗口,是 JavaScript 的顶级对象。
- 我们创建的所有对象、函数、变量都是 Window 对象的成员。
- Window 对象的方法和属性是在全局范围内有效的。
document
- Document 对象是 HTML 文档的根节点与所有其他节点(元素节点,文本节点,属性节点, 注释节点)
- Document 对象使我们可以通过脚本对 HTML 页面中的所有元素进行访问
- Document 对象是 Window 对象的一部分,即 window.document
获得一个 DOM 元素的绝对位置
- offsetTop:返回当前元素相对于其 offsetParent 元素的顶部的距离
- offsetLeft:返回当前元素相对于其 offsetParent 元素的左边的距离
- getBoundingClientRect():返回值是一个 DOMRect 对象,它包含了一组用于描述边框的只读属性——left、top、right 和 bottom,属性单位为像素
offsetWidth/offsetHeight,clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别
offsetWidth/offsetHeight 返回值包含 content + padding + border,效果与 e.getBoundingClientRect()相同
clientWidth/clientHeight 返回值只包含 content + padding,如果有滚动条,也不包含滚动条
scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸
body.offsetHeight :body 总高度。
body.offsetWidth :body 总宽度。
body.clientHeight :body 展示的高度;表示 body 在浏览器内显示的区域高度。
body.clientWidth :body 展示的宽度;表示 body 在浏览器内显示的区域宽度。
滚动条高度/宽度 :如高度,可通过浏览器内页面可用高度 - body 展示高度得出,即 window.innerHeight - body.clientHeight。
BOM
BOM 是 browser object model 的缩写, 简称浏览器对象模型。 主要处理浏览器窗口和框架,描述了与浏览器进行交互的方法和接口, 可以对浏览器窗口进行访问和操作, 譬如可以弹出新的窗口, 回退历史记录, 获取 url。
BOM 对象包含哪些内容?
- Window JavaScript 层级中的顶层对象, 表示浏览器窗口。
- Navigator 包含客户端浏览器的信息。
- History 包含了浏览器窗口访问过的 URL。
- Location 包含了当前 URL 的信息。
- Screen 包含客户端显示屏的信息。
BOM 与 DOM 的关系
- js 是通过访问 BOM 对象来访问、 控制、 修改浏览器
- BOM 的 window 包含了 document, 因此通过 window 对象的 document 属性就可以访问、检索、 修改文档内容与结构。
- document 对象又是 DOM 模型的根节点。
因此, BOM 包含了 DOM, 浏览器提供出来给予访问的是 BOM 对象, 从 BOM 对象再访问到 DOM 对象, 从而 js 可以操作浏览器以及浏览器读取到的文档
History 对象
History 对象包含用户(在浏览器窗口中) 访问过的 URL
| 方法/属性 | 描述 |
|---|---|
| length | 返回浏览器历史列表中的 URL 数量。 |
| back() | 加载 history 列表中的前一个 URL。 |
| forward() | 加载 history 列表中的下一个 URL。 |
| go() | 加载 history 列表中的某个具体页面 |
location 对象
location 对象包含有关当前 URL 的信息。
| 属性 | 描述 |
|---|---|
| hash | 设置或返回从井号 (#) 开始的 URL(锚) 。 |
| host | 设置或返回主机名和当前 URL 的端口号。 |
| hostname | 设置或返回当前 URL 的主机名。 |
| href | 设置或返回完整的 URL。 |
| pathname | 设置或返回当前 URL 的路径部分。 |
| port | 设置或返回当前 URL 的端口号。 |
| protocol | 设置或返回当前 URL 的协议。 |
| search | 置或返回从问号 (?) 开始的 URL(查询部分) 。 |
navigator.userAgent
返回由客户机发送服务器的 user-agent 头部的值。
事件
DOM 事件级别
- DOM0: onXXX 类型的定义事件
- DOM2: addEventListener, removeEventListener
- DOM3: 增加了很多事件类型
DOM 事件模型
捕获从上到下, 冒泡从下到上。先捕获,再到目标,再冒泡
DOM 事件捕获的具体流程: 从 window -> document -> html -> body -> ... -> 目标元素
事件模型
W3C 中定义事件的发生经历三个阶段:捕获阶段(capturing)、目标阶段(targeting)、冒泡阶段(bubbling)
冒泡型事件:当你使用事件冒泡时,子级元素先触发,父级元素后触发 捕获型事件:当你使用事件捕获时,父级元素先触发,子级元素后触发
阻止冒泡:在 W3c 中,使用 stopPropagation()方法;在 IE 下设置 cancelBubble = true 阻止捕获:阻止事件的默认行为,例如 click 后的跳转。在 W3c 中,使用 preventDefault()方法,在 IE 下设置 window.event.returnValue = false
DOM 事件流
事件流分为两种: 捕获事件流和冒泡事件流。
捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点。 冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点。
DOM 事件流分为三个阶段,一个是捕获节点,一个是处于目标节点阶段,一个是冒泡阶段
阻止冒泡事件 event.stopPropagation()
function stopBubble(e) {
if (e && e.stopPropagation) {
// 如果提供了事件对象 event 这说明不是 IE 浏览器
e.stopPropagation();
} else {
window.event.cancelBubble = true; // IE 方式阻止冒泡
}
}
阻止默认行为 event.preventDefault()
function stopDefault(e) {
if (e && e.preventDefault) {
e.preventDefault();
} else {
// IE浏览器阻止函数器默认动作的行为
window.event.returnValue = false;
}
}
事件触发三阶段
事件流动顺序 ?
- document 往事件触发处传播,遇到注册的捕获事件会触发
- 传播到事件触发处时触发注册的事件
- 从事件触发处往 document 传播,遇到注册的冒泡事件会触发
事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个目标节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行
// 以下会先打印冒泡然后是捕获
node.addEventListener(
"click",
(event) => {
console.log("冒泡");
},
false
);
node.addEventListener(
"click",
(event) => {
console.log("捕获 ");
},
true
);
在一个 DOM 上同时绑定两个点击事件:一个用捕获,一个用冒泡。事件会执行几次,先执行冒泡还是捕获?
- 该 DOM 上的事件如果被触发,会执行两次(执行次数等于绑定次数)
- 如果该 DOM 是目标元素,则按事件绑定顺序执行,不区分冒泡/捕获
- 如果该 DOM 是处于事件流中的非目标元素,则先执行捕获,后执行冒泡
addEventListener 有哪些参数?
有三个参数,第一个是事件的类型,第二个是事件的回调函数,第三个是一个表示事件是冒泡阶段还是捕获阶段捕获的布尔值,true 表示捕获,false 表示冒泡
Event 对象常见应用
event.target触发事件的元素event.currentTarget绑定事件的元素event.preventDefault()阻止默认行为event.stopPropagation()阻止在捕获阶段或冒泡阶段继续传播,而不是阻止冒泡event.stopImmediatePropagation()阻止事件冒泡并且阻止相同事件的其他侦听器被调用。
如何派发事件(dispatchEvent)?(如何进行事件广播?)
- W3C: 使用 dispatchEvent 方法
- IE: 使用 fireEvent 方法
var fireEvent = function (element, event) {
if (document.createEventObject) {
var mockEvent = document.createEventObject();
return element.fireEvent("on" + event, mockEvent);
} else {
var mockEvent = document.createEvent("HTMLEvents");
mockEvent.initEvent(event, true, true);
return !element.dispatchEvent(mockEvent);
}
};
自定义事件
- Event
- CustomEvent
var btn = document.querySelector("#btn");
/*
* 第一个参数是事件类型
* 第二个参数是一个对象
*/
var ev = new CustomEvent("alert", {
bubbles: "true",
cancelable: "true",
detail: "button",
});
btn.addEventListener(
"alert",
function (event) {
console.log(event.bubbles); //true
console.log(event.cancelable); //true
console.log(event.detail); //button
},
false
);
btn.dispatchEvent(ev);
注册事件
通常我们使用
addEventListener注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值useCapture参数来说,该参数默认值为false。useCapture决定了注册的事件是捕获事件还是冒泡事件。对于对象参数来说,可以使用以下几个属性:capture:布尔值,和useCapture作用一样once:布尔值,值为true表示该回调只会调用一次,调用后会移除监听passive:布尔值,表示永远不会调用preventDefault
一般来说,我们只希望事件只触发在目标上,这时候可以使用
stopPropagation来阻止事件的进一步传播。通常我们认为stopPropagation是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。stopImmediatePropagation同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件
node.addEventListener(
"click",
(event) => {
event.stopImmediatePropagation();
console.log("冒泡");
},
false
);
// 点击 node 只会执行上面的函数,该函数不会执行
node.addEventListener(
"click",
(event) => {
console.log("捕获 ");
},
true
);
事件代理
事件代理又称之为事件委托。将事件绑定目标元素的到父元素上,利用冒泡机制触发该事件。
优点:
- 可以减少事件注册,节省大量内存占用
- 可以将事件应用于动态添加的子元素上
- 提高运行效率(尤其对于需要循环操作的列表)
- 动态添加后的元素,仍然可以有这些事件(对比与给自己添加的事件,后续动态添加时,新来的这些元素不会有这些事件)
但使用不当会造成事件在不应该触发时触发
ulEl.addEventListener(
"click",
function (e) {
var target = event.target || event.srcElement;
if (target && target.nodeName.toUpperCase() === "LI") {
console.log(target.innerHTML);
}
},
false
);
target 与 currentTarget 的区别?
target 是触发事件的某个具体的对象,是直接接受事件的目标 DOM 元素,即 "谁触发了事件,谁就是 target "。currentTarget 是绑定事件的对象。
- target 只会出现在事件流的目标阶段,currentTarget 可能出现在事件流的任何阶段
- 当事件流处在目标阶段时,二者的指向相同
- 当事件流处于捕获或冒泡阶段时:currentTarget 指向当前事件活动的对象(一般为父级)
事件、IE 与火狐的事件机制有什么区别? 如何阻止冒泡?
- 我们在网页中的某个操作(有的操作对应多个事件)。例如:当我们点击一个按钮就会产生一个事件。是可以被 JavaScript 侦测到的行为。
- 事件处理机制:IE 是事件冒泡、firefox 同时支持两种事件模型,也就是:捕获型事件和冒泡型事件。;
- ev.stopPropagation();注意旧 ie 的方法 ev.cancelBubble = true;
document 中的load事件和DOMContentLoaded事件之间的区别是什么?
初始的 HTML 文档被完全加载和解析完成之后(DOM 树构建完成),DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架等其他资源的完成加载。
window的load事件仅在 DOM 和所有相关资源全部完成加载后(页面加载完毕)才会触发。
onready 比 onload 先执行
执行时间 window.onload 必须等到页面内包括图片的所有元素加载完毕后才能执行。 $(document).ready()是 DOM 结构绘制完毕后就执行,不必等到加载完毕。
onload 只执行最后一个而 onready 可以执行多个。
编写个数不同 window.onload 不能同时编写多个,如果有多个 window.onload 方法,只会执行一个。 $(document).ready()可以同时编写多个,并且都可以得到执行 $(document).ready(function(){})可以简写成$(function(){});
写一个通用的事件侦听器函数
my.Event = {
// 页面加载完成后
readyEvent: function (fn) {
if (fn == null) {
fn = document;
}
var oldonload = window.onload;
if (typeof window.onload != "function") {
window.onload = fn;
} else {
window.onload = function () {
oldonload();
fn();
};
}
},
// 视能力分别使用dom0||dom2||IE方式 来绑定事件
// 参数: 操作的元素,事件名称 ,事件处理程序
addEvent: function (element, type, handler) {
if (element.addEventListener) {
//事件类型、需要执行的函数、是否捕捉
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, function () {
handler.call(element);
});
} else {
element["on" + type] = handler;
}
},
// 移除事件
removeEvent: function (element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.datachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
stopPropagation: function (ev) {
if (ev.stopPropagation) {
ev.stopPropagation();
} else {
ev.cancelBubble = true;
}
},
// 取消事件的默认行为
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
// 获取事件目标
getTarget: function (event) {
return event.target || event.srcElement;
},
// 获取event对象的引用,取到事件的所有信息,确保随时能使用event;
getEvent: function (e) {
var ev = e || window.event;
if (!ev) {
var c = this.getEvent.caller;
while (c) {
ev = c.arguments[0];
if (ev && Event == ev.constructor) {
break;
}
c = c.caller;
}
}
return ev;
},
};
requestIdleCallback 和 requestAnimationFrame 区别
requestIdleCallback
requestIdleCallback是一个兼容性不那么好的功能,所以我们使用前得判断它是否支持- 我们可以使用
window.requestIdleCallback()方法来插入一个函数,这个函数将在浏览器空闲时被调用;requestIdleCallback - 它的参数为
callback和 可选的timeout;如果指定了timeout且为正值;则回调在timeout毫秒后还没调用时,回调任务就会被放入事件循环里排队,这样做可能会影响性能;
注意点:因为它发生在一帧的最后,此时页面布局已经完成,所以不建议在
requestIdleCallback里再操作DOM,这样会导致页面再次重绘。
requestAnimationFrame
window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。- 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行